/*
 * CONSTANTS
 */
var protocolId = Buffer.from([
  0xfe,
  'S'.charCodeAt(0),
  'M'.charCodeAt(0),
  'B'.charCodeAt(0),
]);

var headerTranslates = {
  Command: {
    NEGOTIATE: 0x0000,
    SESSION_SETUP: 0x0001,
    LOGOFF: 0x0002,
    TREE_CONNECT: 0x0003,
    TREE_DISCONNECT: 0x0004,
    CREATE: 0x0005,
    CLOSE: 0x0006,
    FLUSH: 0x0007,
    READ: 0x0008,
    WRITE: 0x0009,
    LOCK: 0x000a,
    IOCTL: 0x000b,
    CANCEL: 0x000c,
    ECHO: 0x000d,
    QUERY_DIRECTORY: 0x000e,
    CHANGE_NOTIFY: 0x000f,
    QUERY_INFO: 0x0010,
    SET_INFO: 0x0011,
    OPLOCK_BREAK: 0x0012,
  },
};

// eslint-disable-next-line no-unused-vars
var flags = {
  SERVER_TO_REDIR: 0x00000001,
  ASYNC_COMMAND: 0x00000002,
  RELATED_OPERATIONS: 0x00000004,
  SIGNED: 0x00000008,
  DFS_OPERATIONS: 0x10000000,
  REPLAY_OPERATION: 0x20000000,
};

var headerLength = 64;

var headerSync = function(processId, sessionId) {
  return [
    ['ProtocolId', 4, protocolId],
    ['StructureSize', 2, headerLength],
    ['CreditCharge', 2, 0],
    ['Status', 4, 0],
    ['Command', 2],
    ['Credit', 2, 126],
    ['Flags', 4, 0],
    ['NextCommand', 4, 0],
    ['MessageId', 4],
    ['MessageIdHigh', 4, 0],
    ['ProcessId', 4, processId],
    ['TreeId', 4, 0],
    ['SessionId', 8, sessionId],
    ['Signature', 16, 0],
  ];
};

var headerASync = function(processId, sessionId) {
  return [
    ['ProtocolId', 4, protocolId],
    ['StructureSize', 2, headerLength],
    ['CreditCharge', 2, 0],
    ['Status', 4, 0],
    ['Command', 2],
    ['Credit', 2, 126],
    ['Flags', 4, 0],
    ['NextCommand', 4, 0],
    ['MessageId', 4],
    ['MessageIdHigh', 4, 0],
    ['AsyncId', 8],
    ['SessionId', 8, sessionId],
    ['Signature', 16, 0],
  ];
};

/*
 * CONSTRUCTOR
 */
var SMB2Message = (module.exports = function(options) {
  // INIT HEADERS
  this.headers = {};
  if (options && options.headers) {
    this.setHeaders(options.headers);
  }

  // INIT REQUEST
  this.request = {};
  if (options && options.request) {
    this.setRequest(options.request);
  }

  // INIT RESPONSE
  this.response = {};
});

var proto = (SMB2Message.prototype = {});

proto.setHeaders = function(obj) {
  for (var key in obj) {
    this.headers[key] = obj[key];
  }
  this.structure = require('../structures/' +
    this.headers['Command'].toLowerCase());
};

proto.getHeaders = function() {
  return this.headers;
};

proto.setRequest = function(request) {
  this.request = request;
};

proto.getResponse = function() {
  return this.response;
};

proto.getBuffer = function(connection) {
  var buffer = Buffer.allocUnsafe(0xffff);
  var length = 0;

  // SET MESSAGE ID
  if (!this.isMessageIdSetted) {
    this.isMessageIdSetted = true;
    this.headers['MessageId'] = connection.messageId++;
  }

  // HEADERS
  length += writeHeaders(this, buffer);

  // REQUEST
  length += writeRequest(this, buffer, headerLength);

  // extract the data
  var output = Buffer.allocUnsafe(length);
  buffer.copy(output, 0, 0, length);
  return output;
};

proto.parseBuffer = function(buffer) {
  // HEADERS
  readHeaders(this, buffer);

  // RESPONSE
  readResponse(this, buffer, headerLength);
};

/*
 * HELPERS
 */
function dataToBuffer(data, length) {
  // buffers will be buffers
  if (Buffer.isBuffer(data)) {
    return data;
  }

  // string to buffer
  if (typeof data === 'string') {
    return Buffer.from(data);
  }

  // raw data to buffer
  var result = Buffer.allocUnsafe(length);
  for (var i = 0; i < length; i++) {
    result.writeUInt8(0xff & (data >> (i * 8)), i);
  }
  return result;
}
function bufferToData(buffer) {
  // not a buffer go away
  if (!Buffer.isBuffer(buffer)) {
    return buffer;
  }

  // raw data to buffer
  var result = 0;
  for (var i = 0; i < buffer.length; i++) {
    result += buffer.readUInt8(i) << (i * 8);
  }
  return result;
}
function writeData(buffer, data, offset, length) {
  dataToBuffer(data, length).copy(buffer, offset, 0);
}
function readData(buffer, offset, length) {
  return buffer.slice(offset, offset + length);
}
function translate(key, value) {
  if (
    headerTranslates[key] &&
    typeof headerTranslates[key][value] !== 'undefined'
  ) {
    return headerTranslates[key][value];
  }
  return value;
}
function unTranslate(key, value) {
  if (headerTranslates[key]) {
    for (var t in headerTranslates[key]) {
      if (headerTranslates[key][t] === value) {
        return t;
      }
    }
  }
  return null;
}

/*
 * PRIVATE FUNCTIONS
 */
function readHeaders(message, buffer) {
  var header = (message.isAsync ? headerASync : headerSync)(
    message.ProcessId,
    message.SessionId
  );
  var offset = 0;
  for (var i in header) {
    var key = header[i][0];
    var length = header[i][1];
    message.headers[key] = readData(buffer, offset, length);
    if (length <= 8) {
      message.headers[key] =
        unTranslate(key, bufferToData(message.headers[key])) ||
        message.headers[key];
    }
    offset += length;
  }
  message.structure = require('../structures/' +
    message.headers['Command'].toLowerCase());
}

function writeHeaders(message, buffer) {
  var header = (message.isAsync ? headerASync : headerSync)(
    message.ProcessId,
    message.SessionId
  );
  var offset = 0;
  for (var i in header) {
    var key = header[i][0];
    var length = header[i][1];
    var defaultValue = header[i][2] || 0;
    writeData(
      buffer,
      translate(key, message.headers[key] || defaultValue),
      offset,
      length
    );
    offset += length;
  }
  return offset;
}

function readResponse(message, buffer, offset) {
  for (var i in message.structure.response) {
    var key = message.structure.response[i][0];
    var length = message.structure.response[i][1] || 1;
    if (typeof length === 'string') {
      length = bufferToData(message.response[length]);
    }
    message.response[key] = readData(buffer, offset, length);
    offset += length;
  }
}

function writeRequest(message, buffer, offset) {
  var initOffset = offset;
  var needsRewrite = false;
  var tmpBuffer = Buffer.allocUnsafe(buffer.length);
  offset = 0;
  for (var i in message.structure.request) {
    var key = message.structure.request[i][0];
    var length = message.structure.request[i][1] || 1;
    var defaultValue = message.structure.request[i][2] || 0;
    if (typeof length === 'string') {
      message.request[key] = message.request[key] || '';
      if (message.request[length] !== message.request[key].length) {
        message.request[length] = message.request[key].length;
        needsRewrite = true;
      }
      length = message.request[key].length;
    } else {
      message.request[key] = message.request[key] || defaultValue;
    }
    writeData(tmpBuffer, message.request[key], offset, length);
    offset += length;
  }
  if (needsRewrite) {
    writeRequest(message, tmpBuffer, 0);
  }
  tmpBuffer.copy(buffer, initOffset, 0, offset);
  return offset;
}
